/**
 * 
 */
package org.ndx.jsg.output.freemarker;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.ndx.jsg.FileInfos;
import org.ndx.jsg.RelativePathProvider;
import org.ndx.jsg.util.MetadataLoader;

import freemarker.template.Template;
import freemarker.template.TemplateException;

public final class TemplateApplier implements DirectoryVisitor, NameResolver {
	public static final String TEMPLATE_DIRPATH = "__dirpath__";
	public static final String TEMPLATE_FILEPATH = "__filepath__";
	public static final String TEMPLATE_FILENAME = "__filename__";
	private static final Logger logger = Logger.getLogger(TemplateApplier.class.getName());
	public static final String TEMPLATE_DIRECTORY = "__directory__";
	private TemplateProvider provider;
	
	private Stack<DirectoryInfos> directoryInfos = new Stack<DirectoryInfos>();

	public TemplateApplier(TemplateProvider provider) {
		this.provider = provider;
	}

	/**
	 * Apply all file infos templates
	 */
	@Override
	public void visit(FileInfos infos) {
		directoryInfos.peek().put(infos);
	}

	@Override
	public void startVisit(Directory directory) {
		directoryInfos.push(new DirectoryInfos(directory, this));
	}

	/**
	 * When finishing visiting a directory, and as a consequence having generated all 
	 */
	@Override
	public void endVisit(Directory directory) {
		StringBuilder relativePathBuilder = new StringBuilder();
		for(DirectoryInfos i : directoryInfos) {
			relativePathBuilder.append(i.directory.getName()).append(File.separator);
		}
		String relativePath = relativePathBuilder.toString();
		DirectoryInfos infos = directoryInfos.pop();
		infos.saveAll(provider, relativePath, this);
	}

	/**
	 * Apply template to the given root map, containing a simple xml document at key "doc", for writing file from relativePath
	 * @param root output root file
	 * @param template template applied
	 * @param relativePath file input relative path to its root
	 * @return the output file
	 * @throws IOException
	 * @throws TemplateException
	 */
	public File applyTemplate(
			Object dataModel, Template template, String relativePath, boolean isFile)
			throws IOException, TemplateException {
		File output = resolveFileName(provider.getOutputRoot(), relativePath, template.getName(), isFile);
		output.getParentFile().mkdirs();
		Writer writer = new FileWriter(output);
		template.process(dataModel, writer);
		writer.flush();
		return output;
	}

	/**
	 * Resolve output file name from an output root, a file relative path, and a
	 * template name. Template name is interpreted the following name :
	 * <dl>
	 * <dt>__filename__</dt>
	 * <dd>will be replaced by file simple name (without extension, that's to
	 * say before the first dot character)</dd>
	 * <dt>__filepath__</dt>
	 * <dd>will be replaced by file qualified simple name (without extension, that's to
	 * say before the first dot character). As an example, a file at relative path /a/b/f.txt will be output by a template named __filepath__.html.template as
	 * /a/b/f.html</dd>
	 * <dt>__dirpath__</dt>
	 * <dd>will be replaced by dir qualified full name (without extension, that's to
	 * say before the first dot character). As an example, a file at relative path /a/b/f will be output by a template named __dirpath__index.html.template as
	 * /a/b/f/index.html. Obviously, it is of more use for directory templates</dd>
	 * <dt>__directory__</dt>
	 * <dd>will be replaced by dir qualified full name (without extension, that's to
	 * say before the first dot character) as a directory. As an example, a folder at relative path /a/b/f will be output by a template named __dirpath__index.html.template as
	 * /a/b/f/index.html. Obviously, it is of more use for directory templates</dd>
	 *</dl> 
	 * @param outputRoot
	 *            output root
	 * @param relativePath
	 *            file path relatively to output root
	 * @param name
	 *            template name
	 * @param true if template applies to a file, false for a directory (believe me, it does help)
	 * @return
	 */
	public static File resolveFileName(String outputRoot, String relativePath,
			String name, boolean isFile) {
		if(isFile)
			return resolveForFileName(outputRoot, relativePath, name);
		else
			return resolveForDirName(outputRoot, relativePath, name);
	}

	
	private static File resolveForDirName(String outputRoot,
			String relativePath, String name) {
		File initialOutputPath = new File(outputRoot + File.separator
				+ relativePath);
		String initialPath = initialOutputPath.getAbsolutePath();
		String fileFullName = initialOutputPath.getName();
		int rootLength = outputRoot.length();
		int initialPathPos = initialPath
				.indexOf(fileFullName);
		boolean isNotRoot = rootLength<initialPathPos;
		String path = isNotRoot ? initialPath.substring(rootLength+File.separator.length(), initialPathPos) : "";
		name = name.replace(TEMPLATE_FILENAME, fileFullName);

		name = name.replace(TEMPLATE_DIRECTORY, isNotRoot ? path+File.separator+fileFullName+File.separator : "");
		// In a directory, path ends with File.separator. remove it !
		name = name.replace(TEMPLATE_FILEPATH, isNotRoot ? path+File.separator+fileFullName : "");
		name = name.replace(TEMPLATE_DIRPATH, path+File.separator);
		// Finally, remove the extension
		name = name.substring(0, name.lastIndexOf("."));
		return new File(outputRoot + (name.startsWith(".") ? "" : File.separator) + name);
	}

	private static File resolveForFileName(String outputRoot,
			String relativePath, String name) {
		File initialOutputPath = new File(outputRoot + File.separator
				+ relativePath);
		String fileFullName = initialOutputPath.getName();
		int dotPos = fileFullName.indexOf(".");
		String fileSimpleName = dotPos >= 0 ? fileFullName.substring(0, dotPos)
				: fileFullName;
		name = name.replace(TEMPLATE_FILENAME, fileSimpleName);
		String initialPath = initialOutputPath.getAbsolutePath();
		int rootLength = outputRoot.length();
		int initialPathPos = initialPath
				.indexOf(fileFullName);
		String path = initialPath.substring(rootLength, initialPathPos);

		name = name.replace(TEMPLATE_FILEPATH, path+File.separator+fileSimpleName);
		name = name.replace(TEMPLATE_DIRPATH, path+File.separator);
		// Finally, remove the extension
		name = name.substring(0, name.lastIndexOf("."));
		return new File(outputRoot + File.separator + name);
	}

	/**
	 * Calls {@link #resolveFileName(String, String, String)} with the correct set of parameters. Notice return value is provided as a relative path
	 */
	@Override
	public String resolve(RelativePathProvider infos, String templateName, boolean isFile) {
		String outputRoot = provider.getOutputRoot();
		File absolute = resolveFileName(outputRoot, infos.getRelativePath(), templateName, isFile);
		String returned = absolute.getAbsolutePath();
		return returned.substring(outputRoot.length());
	}
}